﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using GameWeb.Hubs;
using GameWeb.Models;
using GameDb;
using GameDb.Entities;
using System.Web.Script.Serialization;
using System.Reflection;
using SignalR;
using SignalR.Hosting;
using SignalR.Hubs;
using SignalR.Infrastructure;

namespace GameWeb.Controllers
{
    public enum MapDirection
    {
        NORTH,
        SOUTH,
        EAST,
        WEST
    }
    public class GameController : Controller
    {
        IHubContext context = GlobalHost.ConnectionManager.GetHubContext<TurnNotificationHub>();
 
        //
        // GET: /Game/

        public static void ProcessPieceRun(List<MatchPiece> movedPieceEntities,
            MapDirection direction, MatchPiece curPiece, IQueryable<MatchPiece> PiecesOnBoard,
    ref bool foundInvalidMove, ref bool connectedToMapPiece, List<MatchPiece> scorePieces)
        {
            var done = false;
            var dynamicPropValues = new HashSet<int>();
            var coordFuncs = new Dictionary<MapDirection, Func<int?, int?, Tuple<int, int>>>();
            coordFuncs[MapDirection.NORTH] = (x, y) => { return new Tuple<int, int>((int)x, ((int)y) + -1); };
            coordFuncs[MapDirection.SOUTH] = (x, y) => { return new Tuple<int, int>((int)x, ((int)y) + 1); };
            coordFuncs[MapDirection.EAST] = (x, y) => { return new Tuple<int, int>(((int)x) + -1, (int)y); };
            coordFuncs[MapDirection.WEST] = (x, y) => { return new Tuple<int, int>(((int)x) + 1, (int)y); };
            Func<MatchPiece, int> colorProp = mp => mp.ColorIndex;
            Func<MatchPiece, int> shapeProp = mp => mp.ShapeIndex;
            Action<List<MatchPiece>, MatchPiece> addScorePiece = (scorePiecesTmp, piece) =>
            {
                if (!scorePiecesTmp.Select(sp => sp.MatchPieceId).Contains(piece.MatchPieceId))
                    scorePiecesTmp.Add(piece);
            };
            Func<MatchPiece, int> constantProp = null;
            Func<MatchPiece, int> dynamicProp = null;
            while (!done)
            {
                //get piece in the next position
                MatchPiece newPiece = null;
                var newpos = coordFuncs[direction](curPiece.BoardXpos, curPiece.BoardYpos);
                var newPieceList = PiecesOnBoard.Where(mp => mp.BoardXpos != null && (int)mp.BoardXpos ==
                    newpos.Item1 && (int)mp.BoardYpos == newpos.Item2).ToList();
                if (newPieceList.Count > 0)
                {
                    newPiece = newPieceList[0];
                    if (!movedPieceEntities.Contains(newPiece))
                        connectedToMapPiece = true;
                }
                else
                {
                    done = true;
                    break;
                }
                if (constantProp == null)
                {
                    //if (shapeNdxTmp && colorNdxTmp)
                    if ((curPiece.ShapeIndex == newPiece.ShapeIndex &&
                        curPiece.ColorIndex == newPiece.ColorIndex)
                        || (curPiece.ShapeIndex != newPiece.ShapeIndex &&
                        curPiece.ColorIndex != newPiece.ColorIndex))
                    {
                        //same piece or completely different pieces
                        foundInvalidMove = done = true;
                        break;
                    }
                    else
                    {
                        if (curPiece.ShapeIndex == newPiece.ShapeIndex)
                        {
                            constantProp = mp => mp.ShapeIndex;
                            dynamicProp = mp => mp.ColorIndex;
                        }
                        else
                        {
                            constantProp = mp => mp.ColorIndex;
                            dynamicProp = mp => mp.ShapeIndex;
                        }
                        dynamicPropValues.Add(dynamicProp(curPiece));
                    }
                }
                if (constantProp(curPiece) != constantProp(newPiece))
                {
                    foundInvalidMove = done = true;
                    break;
                }
                if (dynamicPropValues.Contains(dynamicProp(newPiece)))
                {
                    foundInvalidMove = done = true;
                    break;
                }
                dynamicPropValues.Add(dynamicProp(newPiece));
                addScorePiece(scorePieces, curPiece);
                addScorePiece(scorePieces, newPiece);

                curPiece = newPiece;
            }
        }

        public List<MatchPiece> ValidateAndScoreMove(IQueryable<MatchPiece> matchPieces, List<MatchPiece> pieces,
            ref List<List<MatchPiece>> bonusRunPieceList)
        {
            //verify all pieces together
            if (pieces.Count > 0)
            {
                var consCheckOk = NewPiecesConsecutiveCheck(pieces);
                if (!consCheckOk)
                    return new List<MatchPiece>();
            }

            var scorePieces = new List<MatchPiece>();

            var boardEmpty = false;
            if (matchPieces.Where(mp => mp.BoardXpos != null && mp.BoardYpos != null).Count() == 0)
                boardEmpty = true;

            //get entities for moved pieces
            var movedPieceEntitiesJoin = matchPieces.Join(pieces, dbmp => dbmp.MatchPieceId,
                trmp => trmp.MatchPieceId, (dbmp, trmp) =>
                    new { entityPiece = dbmp, trayPiece = trmp }).ToList();

            //update MatchPieces. won't be saved
            movedPieceEntitiesJoin.ForEach(mmp =>
                {
                    mmp.entityPiece.BoardXpos = mmp.trayPiece.BoardXpos;
                    mmp.entityPiece.BoardYpos = mmp.trayPiece.BoardYpos;
                });

            var movedPieceEntities = movedPieceEntitiesJoin.Select(mp => mp.entityPiece).ToList();

            var foundInvalidMove = false;
            var connectedToMapPiece = false;
            var scorePiecesHoriz = new List<MatchPiece>();
            var scorePiecesVert = new List<MatchPiece>();
            foreach (var movePiece in movedPieceEntities)
            {
                //cache current score pieces to compare after to check for bonus
                var scorePiecesCheck = new List<MatchPiece>();
                scorePiecesCheck.AddRange(scorePiecesHoriz);
                var curpos = new Tuple<int, int>((int)movePiece.BoardXpos, (int)movePiece.BoardYpos);
                foreach (var direction in new MapDirection[] { MapDirection.EAST, MapDirection.WEST })
                {
                    ProcessPieceRun(movedPieceEntities, direction, movePiece, matchPieces, ref foundInvalidMove,
                        ref connectedToMapPiece, scorePiecesHoriz);

                    if (foundInvalidMove)
                        break;
                }
                if (foundInvalidMove)
                    break;

                //check for bonus
                if (scorePiecesHoriz.Count - scorePiecesCheck.Count == 6)
                {
                    var bonusPieces = scorePiecesHoriz.Skip(scorePiecesCheck.Count).Take(6).ToList();
                    bonusRunPieceList.Add(bonusPieces);
                }

                //cache current score pieces to compare after to check for bonus
                scorePiecesCheck.Clear();
                scorePiecesCheck.AddRange(scorePiecesVert);
                foreach (var direction in new MapDirection[] { MapDirection.NORTH, MapDirection.SOUTH })
                {
                    ProcessPieceRun(movedPieceEntities, direction, movePiece, matchPieces, ref foundInvalidMove,
                        ref connectedToMapPiece, scorePiecesVert);

                    if (foundInvalidMove)
                        break;
                }
                //check for bonus
                if (scorePiecesVert.Count - scorePiecesCheck.Count == 6)
                {
                    var bonusPieces = scorePiecesVert.Skip(scorePiecesCheck.Count).Take(6).ToList();
                    bonusRunPieceList.Add(bonusPieces);
                }
            }

            scorePieces.AddRange(scorePiecesHoriz);
            scorePieces.AddRange(scorePiecesVert);
            if ((!connectedToMapPiece && !boardEmpty) || foundInvalidMove)
                scorePieces.Clear();

            return scorePieces;

        }

        public bool NewPiecesConsecutiveCheck(List<MatchPiece> pieces)
        {
            //verifies that all one dim values are the same and the max - min of the other
            //matches the number of pieces
            Func<List<MatchPiece>, Func<MatchPiece, int>, Func<MatchPiece, int>, bool>
                consCheck = (pieceList, dim1Prop, dim2Prop) =>
                {
                    var dim1Cnt = pieceList.Select(pc => dim1Prop(pc)).Distinct().Count();
                    var dim2Cnt = (pieceList.Select(pc => dim2Prop(pc)).Max() -
                        pieceList.Select(pc => dim2Prop(pc)).Min()) + 1;
                    if (dim1Cnt == 1 && dim2Cnt == pieceList.Count)
                        return true;
                    else
                        return false;
                };
            var consCheckOk =
                consCheck(pieces, mp => (int)mp.BoardXpos, mp => (int)mp.BoardYpos)
                    || consCheck(pieces, mp => (int)mp.BoardYpos, mp => (int)mp.BoardXpos);
            return consCheckOk;
        }

        [Authorize]
        public JsonResult Move(int gameId, MatchPiece[] pieces)
        {
            //pieces is entire player tray. get pieces that were moved
            var movedTrayPieces = pieces.Where(pc => pc.BoardXpos != null && pc.BoardYpos != null).ToList();
            short moveScore = 0;

            var moveOk = GameCtx.Use(
                ctx =>
                {
                    var match = GameMatch.GameMatchFromId(ctx, gameId);
                    if (movedTrayPieces.Count > 0)
                    {
                        var bonusRuns = new List<List<MatchPiece>>();
                        var scorePieces = ValidateAndScoreMove(match.MatchPieces.AsQueryable(), movedTrayPieces, ref bonusRuns);
                        if (scorePieces.Count == 0)
                            return false;
                        moveScore = (short)(scorePieces.Count + (bonusRuns.Count * 6));
                    }
                    return true;
                });

            if (!moveOk)
                return Json("Move Not Valid");

            var moveTime = DateTime.Now;
            var success = GameCtx.Use(
                (ctx, ts) =>
                {
                    var match = GameMatch.GameMatchFromId(ctx, gameId);
                    var player = GameCtx.GetCurrentUser(ctx);

                    //verify this is the current player's turn
                    var matchTurn = match.CurrentTurn;
                    var currentPlayerTurn = matchTurn.CurrentPlayerTurn();
                    if (currentPlayerTurn.MatchPlayer.User.Username != player.Username)
                        throw new Exception("Move is for wrong player");

                    //get entities for moved pieces
                    var movedPieceEntities = match.MatchPieces.Join(movedTrayPieces, dbmp => dbmp.MatchPieceId,
                        trmp => trmp.MatchPieceId, (dbmp, trmp) => 
                            new { entityPiece = dbmp, trayPiece = trmp }).ToList();

                    //update MatchPieces
                    movedPieceEntities.ForEach(mmp => 
                        {
                            mmp.entityPiece.BoardXpos = mmp.trayPiece.BoardXpos;
                            mmp.entityPiece.BoardYpos = mmp.trayPiece.BoardYpos;
                            //mmp.entityPiece.MatchPlayer = null;

                            var mpt = new MatchPlayerTurnPiece
                            {
                                BoardXpos = (int)mmp.entityPiece.BoardXpos,
                                BoardYpos = (int)mmp.entityPiece.BoardYpos,
                                MatchPiece = mmp.entityPiece
                            };
                            currentPlayerTurn.MatchPlayerTurnPieces.Add(mpt);
                        });

                    //resolve turn
                    currentPlayerTurn.CompleteDate = moveTime;
                    currentPlayerTurn.MoveScore = moveScore;
					
					//get next match player turn to select pieces for
					MatchTurn nextMatchTurn = null;
					MatchPlayerTurn nextMatchPlayerTurn = null;
					var nextMatchTurnQuery = match.MatchTurns.Where(mt => mt.MatchTurnNumber == matchTurn.MatchTurnNumber + 1);
					if (nextMatchTurnQuery.Count() == 0)
					{
						//create next match turn
						nextMatchTurn = new MatchTurn
						{
							MatchTurnNumber = (short)(matchTurn.MatchTurnNumber + (short)1),
							GameMatch = match
						};
						match.MatchTurns.Add(nextMatchTurn);
						
						//mark previous match turn as complete
						//var prevMatchTurnQuery = match.MatchTurns.Where(mt => mt.MatchTurnNumber == matchTurn.MatchTurnNumber - 1);
						//if (prevMatchTurnQuery.Count() > 0)
						//	prevMatchTurnQuery.Single().CompleteDate = moveTime;
						
					}
					else
					{
						nextMatchTurn = nextMatchTurnQuery.Single();
					}
					
					nextMatchPlayerTurn = new MatchPlayerTurn
					{
						MatchTurn = nextMatchTurn,
						TurnSequence = currentPlayerTurn.MatchPlayer.TurnSequence,
						MatchPlayer = currentPlayerTurn.MatchPlayer
					};
					
					nextMatchTurn.MatchPlayerTurns.Add(nextMatchPlayerTurn);
                    currentPlayerTurn.MatchPlayer.MatchPlayerTurns.Add(nextMatchPlayerTurn);
					
					//find next player turn and mark as started
					MatchPlayerTurn newMatchPlayerTurn = null;
					var nextPlayerTurnQuery = matchTurn.MatchPlayerTurns.Where(mt => mt.StartDate == null);
					if (nextPlayerTurnQuery.Count() > 0)
						newMatchPlayerTurn = nextPlayerTurnQuery.OrderBy(np => np.TurnSequence).First();
					else
					{
						//last player has moved for the turn. mark current match turn as ended and next match turn as started
						newMatchPlayerTurn = nextMatchTurn.MatchPlayerTurns.OrderBy(np => np.TurnSequence).First();
						nextMatchTurn.StartDate = moveTime;
						matchTurn.CompleteDate = moveTime;
					}
					
					newMatchPlayerTurn.StartDate = moveTime;

                    //check for last move in match turn
					/*
                    var matchPlayer = currentPlayerTurn.MatchPlayer;
                    short newTurnSeq = (short)(matchPlayer.TurnSequence + 1);
                    if (matchPlayer.TurnSequence == match.MatchPlayers.Count - 1)
                    {
                        matchTurn.CompleteDate = moveTime;
                        short curTurnNumber = matchTurn.MatchTurnNumber;
                        ctx.SaveChanges();
                        matchTurn = new MatchTurn
                        {
                            StartDate = moveTime,
                            GameMatch = match,
                            MatchTurnNumber = (short)(curTurnNumber + (short)1)
                        };
                        ctx.MatchTurns.Add(matchTurn);
                        match.MatchTurns.Add(matchTurn);
                        newTurnSeq = 0;
                    }

                    var nextPlayer = match.MatchPlayers.Where(mp => mp.TurnSequence == newTurnSeq).Single();
                    var nextMatchTurn = new MatchPlayerTurn
                    {
                        StartDate = moveTime,
                        MatchPlayer = nextPlayer,
                        MatchTurn = matchTurn,
                        TurnSequence = newTurnSeq
                    };
                    matchTurn.MatchPlayerTurns.Add(nextMatchTurn);
					*/
                    //select new pieces
                    match.SelectPieces(nextMatchPlayerTurn, match.CountOfEachPieceType - (match.CountOfEachPieceType - movedTrayPieces.Count));

                    var matchId = match.GameMatchId.ToString();
                    context.Clients[matchId].addMessage("Turn Taken");
                    
                    ctx.SaveChanges();
                    return true;
                });
            return Json("");
        }

        [Authorize]
        public ActionResult Play(int id)
        {
            var gameModel = GameCtx.Use(ctx =>
                {
                    var match = GameMatch.GameMatchFromId(ctx, id);
                    var player = GameCtx.GetCurrentUser(ctx);
                    var playerRec = match.GetMatchPlayer(ctx, player);

                    var playerMoves = from q_match in ctx.GameMatches
                                          join q_turns in ctx.MatchTurns on q_match.GameMatchId equals
                                            q_turns.GameMatch.GameMatchId
                                          join q_players in ctx.MatchPlayerTurns on q_turns.MatchTurnId equals
                                            q_players.MatchTurn.MatchTurnId
                                          where q_match.GameMatchId == id
                                            && q_players.CompleteDate != null
                                      //join q_moves in ctx.MatchPlayerTurnPieces on q_players.MatchPlayerTurnId equals
                                          //  q_moves.MatchPlayerTurn.MatchPlayerTurnId
                                          orderby q_turns.MatchTurnNumber, q_players.TurnSequence
                                          select new
                                          {
                                              PlayerName = q_players.MatchPlayer.User.Username,
                                              Moves = q_players.MatchPlayerTurnPieces.Select(mptp => new
                                              {
                                                  BoardXPos = mptp.BoardXpos,
                                                  BoardYPos = mptp.BoardYpos
                                              }),
                                              MatchPlayerTurnId = q_players.MatchPlayerTurnId
                                          };

                    var trayPieces = Json(match.TrayPieces().Join(playerRec.MatchPlayerTurns,
                        mp => mp.MatchPlayerTurn.MatchPlayerTurnId, mpt => mpt.MatchPlayerTurnId, (mp, mpt) => new MatchPiece
                    {
                        BoardXpos = mp.BoardXpos,
                        BoardYpos = mp.BoardYpos,
                        ColorIndex = mp.ColorIndex,
                        MatchPieceId = mp.MatchPieceId,
                        ShapeIndex = mp.ShapeIndex
                    })).Data;
                    //var trayPieces = Json(playerRec.MatchPieces.Where
                    //    (pmp => pmp.BoardXpos == -1 && pmp.BoardYpos == -1).Select(mp => new MatchPiece
                    //{
                    //    BoardXpos = mp.BoardXpos,
                    //    BoardYpos = mp.BoardYpos,
                    //    ColorIndex = mp.ColorIndex,
                    //    MatchPieceId = mp.MatchPieceId,
                    //    ShapeIndex = mp.ShapeIndex
                    //})).Data;
                    var boardPieces = Json(match.MatchPieces.Where(mp => mp.BoardXpos != null &&
                        mp.BoardYpos != null).Select(mp => new MatchPiece
                        {
                            MatchPieceId = mp.MatchPieceId,
                            BoardXpos = mp.BoardXpos,
                            BoardYpos = mp.BoardYpos,
                            ColorIndex = mp.ColorIndex,
                            ShapeIndex = mp.ShapeIndex
                        })).Data;
                    var trayPiecesJson = new JavaScriptSerializer().Serialize(trayPieces);
                    var boardPiecesJson = new JavaScriptSerializer().Serialize(boardPieces);
                    var curMatchTurn = match.CurrentTurn;
                    var curMatchPlayerTurn = curMatchTurn.CurrentPlayerTurn();
                    var game = new GameModel
                    {
                        GameId = id,
                        BoardHeight = 200,
                        BoardWidth = 200,
                        TrayPiecesJson = trayPiecesJson,
                        CurrentUser = MvcApplication.GetCurrentUserName(),
                        PlayerNames = match.MatchPlayers.OrderBy(mp => mp.TurnSequence).
                            Select(mp => new PlayerInfoModel
                            {
                                PlayerName = mp.User.Username,
                                Score = (short)mp.MatchPlayerTurns.Sum(mpt => mpt.MoveScore)
                            }).ToArray(),
                        MatchPlayerTurnId = curMatchPlayerTurn.MatchPlayerTurnId,
                        CurrentTurnUser = curMatchPlayerTurn.MatchPlayer.User.Username,
                        BoardPiecesJson = boardPiecesJson,
                        RemainingPieces = match.PiecesInBag().Count(),
                        PlayerMovesJson = new JavaScriptSerializer().Serialize(playerMoves)
                    };
                    return game;
                });
            return View(gameModel);
        }

        [Authorize]
        public JsonResult BackoutLastTurn(int id)
        {
            var success = GameCtx.Use(
                (ctx, ts) =>
                {
                    var match = GameMatch.GameMatchFromId(ctx, id);

                    var matchTurn = match.CurrentTurn;
                    var currentPlayerTurn = matchTurn.CurrentPlayerTurn();
                    //if (currentPlayerTurn.MatchPlayer.User.Username != player.Username)
                    //    throw new Exception("Move is for wrong player");

                    MatchTurn newMatchTurn = null;
                    MatchPlayerTurn newMatchPlayerTurn = null;
					//can't remove last remaining match turn or any of it's player turns
					if (matchTurn.MatchTurnNumber == 0)
					{
						//if this is the only started match turn, bail out
						if (matchTurn.MatchPlayerTurns.Where(mpt => mpt.StartDate != null).Count() == 1)
							return false;
						currentPlayerTurn.StartDate = null;
						newMatchPlayerTurn = matchTurn.MatchPlayerTurns.Where(mpt => mpt.CompleteDate != null).
							OrderByDescending(mpt => mpt.TurnSequence).First();
						newMatchTurn = matchTurn;
					}
					else
					{
						//this is NOT rhe first match turn
						currentPlayerTurn.StartDate = null;
						if (matchTurn.MatchPlayerTurns.Where(mpt => mpt.CompleteDate != null).Count() == 0)
						{
							//no player turns left. rollback the match turn
							//match.MatchTurns.Remove(matchTurn);
							matchTurn.StartDate = null;
							//get previous match turn
							newMatchTurn = match.MatchTurns.OrderByDescending(mt => mt.MatchTurnNumber == matchTurn.MatchTurnNumber - 1).First();
							newMatchTurn.CompleteDate = null;
							newMatchPlayerTurn = newMatchTurn.MatchPlayerTurns.OrderByDescending(mpt => mpt.TurnSequence).First();
						}
						else
						{
							newMatchTurn = matchTurn;
							newMatchPlayerTurn = matchTurn.MatchPlayerTurns.Where(mpt => mpt.TurnSequence == 
								currentPlayerTurn.TurnSequence - 1).Single();
						}
					}
					
					//newMatchPlayerTurn is now the previous player's match turn record
					newMatchPlayerTurn.CompleteDate = null;
                    newMatchPlayerTurn.MoveScore = null;

					//put pieces selected for this turn back in the bag
					//get the next match turn for the previous player
					var playerSelTurn = match.MatchTurns.Where(mt => mt.MatchTurnNumber == 
						newMatchPlayerTurn.MatchTurn.MatchTurnNumber + 1).Single();
					var nextTurnForPrevPlayer = playerSelTurn.MatchPlayerTurns.Where(mpt => 
						mpt.TurnSequence == newMatchPlayerTurn.TurnSequence).Single();
					foreach (var selPiece in nextTurnForPrevPlayer.MatchPieces)
					{
						selPiece.MatchPlayerTurn = null;
					}
                    playerSelTurn.MatchPlayerTurns.Remove(nextTurnForPrevPlayer);
					
					//put pieces mioved the previous player turn back in the tray
                    foreach (var mptp in newMatchPlayerTurn.MatchPlayerTurnPieces)
                    {
                        mptp.MatchPiece.BoardXpos = mptp.MatchPiece.BoardYpos = null;
                    }
                    newMatchPlayerTurn.MatchPlayerTurnPieces.Clear();

                    ctx.SaveChanges();

                    return true;
                });
            return Json("");
        }

    }
}
